/* *
 *
 *  (c) 2010-2019 Torstein Honsi
 *
 *  License: www.highcharts.com/license
 *
 *  !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
 *
 * */
"use strict";
import H from "./Globals.js";
import U from "./Utilities.js";
var defined = U.defined,
  erase = U.erase,
  extend = U.extend,
  isArray = U.isArray,
  isNumber = U.isNumber,
  isObject = U.isObject,
  isString = U.isString,
  objectEach = U.objectEach,
  pick = U.pick,
  relativeLength = U.relativeLength,
  setAnimation = U.setAnimation,
  splat = U.splat;
import "./Axis.js";
import "./Chart.js";
import "./Point.js";
import "./Series.js";
var addEvent = H.addEvent,
  animate = H.animate,
  Axis = H.Axis,
  Chart = H.Chart,
  createElement = H.createElement,
  css = H.css,
  fireEvent = H.fireEvent,
  merge = H.merge,
  Point = H.Point,
  Series = H.Series,
  seriesTypes = H.seriesTypes;
/* eslint-disable valid-jsdoc */
/**
 * Remove settings that have not changed, to avoid unnecessary rendering or
 * computing (#9197).
 * @private
 */
H.cleanRecursively = function (newer, older) {
  var result = {};
  objectEach(newer, function (val, key) {
    var ob;
    // Dive into objects (except DOM nodes)
    if (
      isObject(newer[key], true) &&
      !newer.nodeType && // #10044
      older[key]
    ) {
      ob = H.cleanRecursively(newer[key], older[key]);
      if (Object.keys(ob).length) {
        result[key] = ob;
      }
      // Arrays, primitives and DOM nodes are copied directly
    } else if (isObject(newer[key]) || newer[key] !== older[key]) {
      result[key] = newer[key];
    }
  });
  return result;
};
// Extend the Chart prototype for dynamic methods
extend(
  Chart.prototype,
  /** @lends Highcharts.Chart.prototype */ {
    /**
     * Add a series to the chart after render time. Note that this method should
     * never be used when adding data synchronously at chart render time, as it
     * adds expense to the calculations and rendering. When adding data at the
     * same time as the chart is initialized, add the series as a configuration
     * option instead. With multiple axes, the `offset` is dynamically adjusted.
     *
     * @sample highcharts/members/chart-addseries/
     *         Add a series from a button
     * @sample stock/members/chart-addseries/
     *         Add a series in Highstock
     *
     * @function Highcharts.Chart#addSeries
     *
     * @param {Highcharts.SeriesOptionsType} options
     *        The config options for the series.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after adding.
     *
     * @param {boolean|Highcharts.AnimationOptionsObject} [animation]
     *        Whether to apply animation, and optionally animation
     *        configuration.
     *
     * @return {Highcharts.Series}
     *         The newly created series object.
     *
     * @fires Highcharts.Chart#event:addSeries
     * @fires Highcharts.Chart#event:afterAddSeries
     */
    addSeries: function (options, redraw, animation) {
      var series,
        chart = this;
      if (options) {
        // <- not necessary
        redraw = pick(redraw, true); // defaults to true
        fireEvent(chart, "addSeries", { options: options }, function () {
          series = chart.initSeries(options);
          chart.isDirtyLegend = true;
          chart.linkSeries();
          if (series.enabledDataSorting) {
            // We need to call `setData` after `linkSeries`
            series.setData(options.data, false);
          }
          fireEvent(chart, "afterAddSeries", { series: series });
          if (redraw) {
            chart.redraw(animation);
          }
        });
      }
      return series;
    },
    /**
     * Add an axis to the chart after render time. Note that this method should
     * never be used when adding data synchronously at chart render time, as it
     * adds expense to the calculations and rendering. When adding data at the
     * same time as the chart is initialized, add the axis as a configuration
     * option instead.
     *
     * @sample highcharts/members/chart-addaxis/
     *         Add and remove axes
     *
     * @function Highcharts.Chart#addAxis
     *
     * @param {Highcharts.AxisOptions} options
     *        The axis options.
     *
     * @param {boolean} [isX=false]
     *        Whether it is an X axis or a value axis.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after adding.
     *
     * @param {boolean|Highcharts.AnimationOptionsObject} [animation=true]
     *        Whether and how to apply animation in the redraw.
     *
     * @return {Highcharts.Axis}
     *         The newly generated Axis object.
     */
    addAxis: function (options, isX, redraw, animation) {
      return this.createAxis(isX ? "xAxis" : "yAxis", {
        axis: options,
        redraw: redraw,
        animation: animation,
      });
    },
    /**
     * Add a color axis to the chart after render time. Note that this method
     * should never be used when adding data synchronously at chart render time,
     * as it adds expense to the calculations and rendering. When adding data at
     * the same time as the chart is initialized, add the axis as a
     * configuration option instead.
     *
     * @sample highcharts/members/chart-addaxis/
     *         Add and remove axes
     *
     * @function Highcharts.Chart#addColorAxis
     *
     * @param {Highcharts.ColorAxisOptions} options
     *        The axis options.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after adding.
     *
     * @param {boolean|Highcharts.AnimationOptionsObject} [animation=true]
     *        Whether and how to apply animation in the redraw.
     *
     * @return {Highcharts.ColorAxis}
     *         The newly generated Axis object.
     */
    addColorAxis: function (options, redraw, animation) {
      return this.createAxis("colorAxis", {
        axis: options,
        redraw: redraw,
        animation: animation,
      });
    },
    /**
     * Factory for creating different axis types.
     *
     * @private
     * @function Highcharts.Chart#createAxis
     *
     * @param {string} type
     *        An axis type.
     *
     * @param {...Array<*>} arguments
     *        All arguments for the constructor.
     *
     * @return {Highcharts.Axis | Highcharts.ColorAxis}
     *         The newly generated Axis object.
     */
    createAxis: function (type, options) {
      var chartOptions = this.options,
        isColorAxis = type === "colorAxis",
        axisOptions = options.axis,
        redraw = options.redraw,
        animation = options.animation,
        userOptions = merge(axisOptions, {
          index: this[type].length,
          isX: type === "xAxis",
        }),
        axis;
      if (isColorAxis) {
        axis = new H.ColorAxis(this, userOptions);
      } else {
        axis = new Axis(this, userOptions);
      }
      // Push the new axis options to the chart options
      chartOptions[type] = splat(chartOptions[type] || {});
      chartOptions[type].push(userOptions);
      if (isColorAxis) {
        this.isDirtyLegend = true;
        // Clear before 'bindAxes' (#11924)
        this.axes.forEach(function (axis) {
          axis.series = [];
        });
        this.series.forEach(function (series) {
          series.bindAxes();
          series.isDirtyData = true;
        });
      }
      if (pick(redraw, true)) {
        this.redraw(animation);
      }
      return axis;
    },
    /**
     * Dim the chart and show a loading text or symbol. Options for the loading
     * screen are defined in {@link
     * https://api.highcharts.com/highcharts/loading|the loading options}.
     *
     * @sample highcharts/members/chart-hideloading/
     *         Show and hide loading from a button
     * @sample highcharts/members/chart-showloading/
     *         Apply different text labels
     * @sample stock/members/chart-show-hide-loading/
     *         Toggle loading in Highstock
     *
     * @function Highcharts.Chart#showLoading
     *
     * @param {string} [str]
     *        An optional text to show in the loading label instead of the
     *        default one. The default text is set in
     *        [lang.loading](http://api.highcharts.com/highcharts/lang.loading).
     *
     * @return {void}
     */
    showLoading: function (str) {
      var chart = this,
        options = chart.options,
        loadingDiv = chart.loadingDiv,
        loadingOptions = options.loading,
        setLoadingSize = function () {
          if (loadingDiv) {
            css(loadingDiv, {
              left: chart.plotLeft + "px",
              top: chart.plotTop + "px",
              width: chart.plotWidth + "px",
              height: chart.plotHeight + "px",
            });
          }
        };
      // create the layer at the first call
      if (!loadingDiv) {
        chart.loadingDiv = loadingDiv = createElement(
          "div",
          {
            className: "highcharts-loading highcharts-loading-hidden",
          },
          null,
          chart.container
        );
        chart.loadingSpan = createElement(
          "span",
          { className: "highcharts-loading-inner" },
          null,
          loadingDiv
        );
        addEvent(chart, "redraw", setLoadingSize); // #1080
      }
      loadingDiv.className = "highcharts-loading";
      // Update text
      chart.loadingSpan.innerHTML = pick(str, options.lang.loading, "");
      if (!chart.styledMode) {
        // Update visuals
        css(
          loadingDiv,
          extend(loadingOptions.style, {
            zIndex: 10,
          })
        );
        css(chart.loadingSpan, loadingOptions.labelStyle);
        // Show it
        if (!chart.loadingShown) {
          css(loadingDiv, {
            opacity: 0,
            display: "",
          });
          animate(
            loadingDiv,
            {
              opacity: loadingOptions.style.opacity || 0.5,
            },
            {
              duration: loadingOptions.showDuration || 0,
            }
          );
        }
      }
      chart.loadingShown = true;
      setLoadingSize();
    },
    /**
     * Hide the loading layer.
     *
     * @see Highcharts.Chart#showLoading
     *
     * @sample highcharts/members/chart-hideloading/
     *         Show and hide loading from a button
     * @sample stock/members/chart-show-hide-loading/
     *         Toggle loading in Highstock
     *
     * @function Highcharts.Chart#hideLoading
     *
     * @return {void}
     */
    hideLoading: function () {
      var options = this.options,
        loadingDiv = this.loadingDiv;
      if (loadingDiv) {
        loadingDiv.className = "highcharts-loading highcharts-loading-hidden";
        if (!this.styledMode) {
          animate(
            loadingDiv,
            {
              opacity: 0,
            },
            {
              duration: options.loading.hideDuration || 100,
              complete: function () {
                css(loadingDiv, { display: "none" });
              },
            }
          );
        }
      }
      this.loadingShown = false;
    },
    /**
     * These properties cause isDirtyBox to be set to true when updating. Can be
     * extended from plugins.
     */
    propsRequireDirtyBox: [
      "backgroundColor",
      "borderColor",
      "borderWidth",
      "borderRadius",
      "plotBackgroundColor",
      "plotBackgroundImage",
      "plotBorderColor",
      "plotBorderWidth",
      "plotShadow",
      "shadow",
    ],
    /**
     * These properties require a full reflow of chart elements, best
     * implemented through running `Chart.setSize` internally (#8190).
     * @type {Array}
     */
    propsRequireReflow: [
      "margin",
      "marginTop",
      "marginRight",
      "marginBottom",
      "marginLeft",
      "spacing",
      "spacingTop",
      "spacingRight",
      "spacingBottom",
      "spacingLeft",
    ],
    /**
     * These properties cause all series to be updated when updating. Can be
     * extended from plugins.
     */
    propsRequireUpdateSeries: [
      "chart.inverted",
      "chart.polar",
      "chart.ignoreHiddenSeries",
      "chart.type",
      "colors",
      "plotOptions",
      "time",
      "tooltip",
    ],
    /**
     * These collections (arrays) implement update() methods with support for
     * one-to-one option.
     */
    collectionsWithUpdate: ["xAxis", "yAxis", "zAxis", "series"],
    /**
     * A generic function to update any element of the chart. Elements can be
     * enabled and disabled, moved, re-styled, re-formatted etc.
     *
     * A special case is configuration objects that take arrays, for example
     * [xAxis](https://api.highcharts.com/highcharts/xAxis),
     * [yAxis](https://api.highcharts.com/highcharts/yAxis) or
     * [series](https://api.highcharts.com/highcharts/series). For these
     * collections, an `id` option is used to map the new option set to an
     * existing object. If an existing object of the same id is not found, the
     * corresponding item is updated. So for example, running `chart.update`
     * with a series item without an id, will cause the existing chart's series
     * with the same index in the series array to be updated. When the
     * `oneToOne` parameter is true, `chart.update` will also take care of
     * adding and removing items from the collection. Read more under the
     * parameter description below.
     *
     * Note that when changing series data, `chart.update` may mutate the passed
     * data options.
     *
     * See also the
     * [responsive option set](https://api.highcharts.com/highcharts/responsive).
     * Switching between `responsive.rules` basically runs `chart.update` under
     * the hood.
     *
     * @sample highcharts/members/chart-update/
     *         Update chart geometry
     *
     * @function Highcharts.Chart#update
     *
     * @param {Highcharts.Options} options
     *        A configuration object for the new chart options.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart.
     *
     * @param {boolean} [oneToOne=false]
     *        When `true`, the `series`, `xAxis`, `yAxis` and `annotations`
     *        collections will be updated one to one, and items will be either
     *        added or removed to match the new updated options. For example,
     *        if the chart has two series and we call `chart.update` with a
     *        configuration containing three series, one will be added. If we
     *        call `chart.update` with one series, one will be removed. Setting
     *        an empty `series` array will remove all series, but leaving out
     *        the`series` property will leave all series untouched. If the
     *        series have id's, the new series options will be matched by id,
     *        and the remaining ones removed.
     *
     * @param {boolean|Highcharts.AnimationOptionsObject} [animation=true]
     *        Whether to apply animation, and optionally animation
     *        configuration.
     *
     * @return {void}
     *
     * @fires Highcharts.Chart#event:update
     * @fires Highcharts.Chart#event:afterUpdate
     */
    update: function (options, redraw, oneToOne, animation) {
      var chart = this,
        adders = {
          credits: "addCredits",
          title: "setTitle",
          subtitle: "setSubtitle",
          caption: "setCaption",
        },
        optionsChart,
        updateAllAxes,
        updateAllSeries,
        newWidth,
        newHeight,
        runSetSize,
        isResponsiveOptions = options.isResponsiveOptions,
        itemsForRemoval = [];
      fireEvent(chart, "update", { options: options });
      // If there are responsive rules in action, undo the responsive rules
      // before we apply the updated options and replay the responsive rules
      // on top from the chart.redraw function (#9617).
      if (!isResponsiveOptions) {
        chart.setResponsive(false, true);
      }
      options = H.cleanRecursively(options, chart.options);
      merge(true, chart.userOptions, options);
      // If the top-level chart option is present, some special updates are
      // required
      optionsChart = options.chart;
      if (optionsChart) {
        merge(true, chart.options.chart, optionsChart);
        // Setter function
        if ("className" in optionsChart) {
          chart.setClassName(optionsChart.className);
        }
        if ("reflow" in optionsChart) {
          chart.setReflow(optionsChart.reflow);
        }
        if (
          "inverted" in optionsChart ||
          "polar" in optionsChart ||
          "type" in optionsChart
        ) {
          // Parse options.chart.inverted and options.chart.polar together
          // with the available series.
          chart.propFromSeries();
          updateAllAxes = true;
        }
        if ("alignTicks" in optionsChart) {
          // #6452
          updateAllAxes = true;
        }
        objectEach(optionsChart, function (val, key) {
          if (chart.propsRequireUpdateSeries.indexOf("chart." + key) !== -1) {
            updateAllSeries = true;
          }
          // Only dirty box
          if (chart.propsRequireDirtyBox.indexOf(key) !== -1) {
            chart.isDirtyBox = true;
          }
          // Chart setSize
          if (
            !isResponsiveOptions &&
            chart.propsRequireReflow.indexOf(key) !== -1
          ) {
            runSetSize = true;
          }
        });
        if (!chart.styledMode && "style" in optionsChart) {
          chart.renderer.setStyle(optionsChart.style);
        }
      }
      // Moved up, because tooltip needs updated plotOptions (#6218)
      if (!chart.styledMode && options.colors) {
        this.options.colors = options.colors;
      }
      if (options.plotOptions) {
        merge(true, this.options.plotOptions, options.plotOptions);
      }
      // Maintaining legacy global time. If the chart is instanciated first
      // with global time, then updated with time options, we need to create a
      // new Time instance to avoid mutating the global time (#10536).
      if (options.time && this.time === H.time) {
        this.time = new H.Time(options.time);
      }
      // Some option stuctures correspond one-to-one to chart objects that
      // have update methods, for example
      // options.credits => chart.credits
      // options.legend => chart.legend
      // options.title => chart.title
      // options.tooltip => chart.tooltip
      // options.subtitle => chart.subtitle
      // options.mapNavigation => chart.mapNavigation
      // options.navigator => chart.navigator
      // options.scrollbar => chart.scrollbar
      objectEach(options, function (val, key) {
        if (chart[key] && typeof chart[key].update === "function") {
          chart[key].update(val, false);
          // If a one-to-one object does not exist, look for an adder function
        } else if (typeof chart[adders[key]] === "function") {
          chart[adders[key]](val);
        }
        if (
          key !== "chart" &&
          chart.propsRequireUpdateSeries.indexOf(key) !== -1
        ) {
          updateAllSeries = true;
        }
      });
      // Setters for collections. For axes and series, each item is referred
      // by an id. If the id is not found, it defaults to the corresponding
      // item in the collection, so setting one series without an id, will
      // update the first series in the chart. Setting two series without
      // an id will update the first and the second respectively (#6019)
      // chart.update and responsive.
      this.collectionsWithUpdate.forEach(function (coll) {
        var indexMap;
        if (options[coll]) {
          // In stock charts, the navigator series are also part of the
          // chart.series array, but those series should not be handled
          // here (#8196).
          if (coll === "series") {
            indexMap = [];
            chart[coll].forEach(function (s, i) {
              if (!s.options.isInternal) {
                indexMap.push(pick(s.options.index, i));
              }
            });
          }
          splat(options[coll]).forEach(function (newOptions, i) {
            var item =
              (defined(newOptions.id) && chart.get(newOptions.id)) ||
              chart[coll][indexMap ? indexMap[i] : i];
            if (item && item.coll === coll) {
              item.update(newOptions, false);
              if (oneToOne) {
                item.touched = true;
              }
            }
            // If oneToOne and no matching item is found, add one
            if (!item && oneToOne && chart.collectionsWithInit[coll]) {
              chart.collectionsWithInit[coll][0].apply(
                chart,
                // [newOptions, ...extraArguments, redraw=false]
                [newOptions]
                  .concat(
                    // Not all initializers require extra args
                    chart.collectionsWithInit[coll][1] || []
                  )
                  .concat([false])
              ).touched = true;
            }
          });
          // Add items for removal
          if (oneToOne) {
            chart[coll].forEach(function (item) {
              if (!item.touched && !item.options.isInternal) {
                itemsForRemoval.push(item);
              } else {
                delete item.touched;
              }
            });
          }
        }
      });
      itemsForRemoval.forEach(function (item) {
        if (item.remove) {
          item.remove(false);
        }
      });
      if (updateAllAxes) {
        chart.axes.forEach(function (axis) {
          axis.update({}, false);
        });
      }
      // Certain options require the whole series structure to be thrown away
      // and rebuilt
      if (updateAllSeries) {
        chart.getSeriesOrderByLinks().forEach(function (series) {
          // Avoid removed navigator series
          if (series.chart) {
            series.update({}, false);
          }
        }, this);
      }
      // For loading, just update the options, do not redraw
      if (options.loading) {
        merge(true, chart.options.loading, options.loading);
      }
      // Update size. Redraw is forced.
      newWidth = optionsChart && optionsChart.width;
      newHeight = optionsChart && optionsChart.height;
      if (isString(newHeight)) {
        newHeight = relativeLength(newHeight, newWidth || chart.chartWidth);
      }
      if (
        // In this case, run chart.setSize with newWidth and newHeight which
        // are undefined, only for reflowing chart elements because margin
        // or spacing has been set (#8190)
        runSetSize ||
        // In this case, the size is actually set
        (isNumber(newWidth) && newWidth !== chart.chartWidth) ||
        (isNumber(newHeight) && newHeight !== chart.chartHeight)
      ) {
        chart.setSize(newWidth, newHeight, animation);
      } else if (pick(redraw, true)) {
        chart.redraw(animation);
      }
      fireEvent(chart, "afterUpdate", {
        options: options,
        redraw: redraw,
        animation: animation,
      });
    },
    /**
     * Shortcut to set the subtitle options. This can also be done from {@link
     * Chart#update} or {@link Chart#setTitle}.
     *
     * @function Highcharts.Chart#setSubtitle
     *
     * @param {Highcharts.SubtitleOptions} options
     *        New subtitle options. The subtitle text itself is set by the
     *        `options.text` property.
     *
     * @return {void}
     */
    setSubtitle: function (options, redraw) {
      this.applyDescription("subtitle", options);
      this.layOutTitles(redraw);
    },
    /**
     * Set the caption options. This can also be done from {@link
     * Chart#update}.
     *
     * @function Highcharts.Chart#setCaption
     *
     * @param {Highcharts.CaptionOptions} options
     *        New caption options. The caption text itself is set by the
     *        `options.text` property.
     *
     * @return {void}
     */
    setCaption: function (options, redraw) {
      this.applyDescription("caption", options);
      this.layOutTitles(redraw);
    },
  }
);
/**
 * These collections (arrays) implement `Chart.addSomethig` method used in
 * chart.update() to create new object in the collection. Equivalent for
 * deleting is resolved by simple `Somethig.remove()`.
 *
 * Note: We need to define these references after initializers are bound to
 * chart's prototype.
 */
Chart.prototype.collectionsWithInit = {
  // collectionName: [ initializingMethod, [extraArguments] ]
  xAxis: [Chart.prototype.addAxis, [true]],
  yAxis: [Chart.prototype.addAxis, [false]],
  series: [Chart.prototype.addSeries],
};
// extend the Point prototype for dynamic methods
extend(
  Point.prototype,
  /** @lends Highcharts.Point.prototype */ {
    /**
     * Update point with new options (typically x/y data) and optionally redraw
     * the series.
     *
     * @sample highcharts/members/point-update-column/
     *         Update column value
     * @sample highcharts/members/point-update-pie/
     *         Update pie slice
     * @sample maps/members/point-update/
     *         Update map area value in Highmaps
     *
     * @function Highcharts.Point#update
     *
     * @param {Highcharts.PointOptionsType} options
     *        The point options. Point options are handled as described under
     *        the `series.type.data` item for each series type. For example
     *        for a line series, if options is a single number, the point will
     *        be given that number as the marin y value. If it is an array, it
     *        will be interpreted as x and y values respectively. If it is an
     *        object, advanced options are applied.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after the point is updated. If doing
     *        more operations on the chart, it is best practice to set
     *        `redraw` to false and call `chart.redraw()` after.
     *
     * @param {boolean|Highcharts.AnimationOptionsObject} [animation=true]
     *        Whether to apply animation, and optionally animation
     *        configuration.
     *
     * @return {void}
     *
     * @fires Highcharts.Point#event:update
     */
    update: function (options, redraw, animation, runEvent) {
      var point = this,
        series = point.series,
        graphic = point.graphic,
        i,
        chart = series.chart,
        seriesOptions = series.options;
      redraw = pick(redraw, true);
      /**
       * @private
       */
      function update() {
        point.applyOptions(options);
        // Update visuals
        if (point.y === null && graphic) {
          // #4146
          point.graphic = graphic.destroy();
        }
        if (isObject(options, true)) {
          // Destroy so we can get new elements
          if (graphic && graphic.element) {
            // "null" is also a valid symbol
            if (
              options &&
              options.marker &&
              typeof options.marker.symbol !== "undefined"
            ) {
              point.graphic = graphic.destroy();
            }
          }
          if (options && options.dataLabels && point.dataLabel) {
            point.dataLabel = point.dataLabel.destroy(); // #2468
          }
          if (point.connector) {
            point.connector = point.connector.destroy(); // #7243
          }
        }
        // record changes in the parallel arrays
        i = point.index;
        series.updateParallelArrays(point, i);
        // Record the options to options.data. If the old or the new config
        // is an object, use point options, otherwise use raw options
        // (#4701, #4916).
        seriesOptions.data[i] =
          isObject(seriesOptions.data[i], true) || isObject(options, true)
            ? point.options
            : pick(options, seriesOptions.data[i]);
        // redraw
        series.isDirty = series.isDirtyData = true;
        if (!series.fixedBox && series.hasCartesianSeries) {
          // #1906, #2320
          chart.isDirtyBox = true;
        }
        if (seriesOptions.legendType === "point") {
          // #1831, #1885
          chart.isDirtyLegend = true;
        }
        if (redraw) {
          chart.redraw(animation);
        }
      }
      // Fire the event with a default handler of doing the update
      if (runEvent === false) {
        // When called from setData
        update();
      } else {
        point.firePointEvent("update", { options: options }, update);
      }
    },
    /**
     * Remove a point and optionally redraw the series and if necessary the axes
     *
     * @sample highcharts/plotoptions/series-point-events-remove/
     *         Remove point and confirm
     * @sample highcharts/members/point-remove/
     *         Remove pie slice
     * @sample maps/members/point-remove/
     *         Remove selected points in Highmaps
     *
     * @function Highcharts.Point#remove
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart or wait for an explicit call. When
     *        doing more operations on the chart, for example running
     *        `point.remove()` in a loop, it is best practice to set `redraw`
     *        to false and call `chart.redraw()` after.
     *
     * @param {boolean|Highcharts.AnimationOptionsObject} [animation=false]
     *        Whether to apply animation, and optionally animation
     *        configuration.
     *
     * @return {void}
     */
    remove: function (redraw, animation) {
      this.series.removePoint(
        this.series.data.indexOf(this),
        redraw,
        animation
      );
    },
  }
);
// Extend the series prototype for dynamic methods
extend(
  Series.prototype,
  /** @lends Series.prototype */ {
    /**
     * Add a point to the series after render time. The point can be added at
     * the end, or by giving it an X value, to the start or in the middle of the
     * series.
     *
     * @sample highcharts/members/series-addpoint-append/
     *         Append point
     * @sample highcharts/members/series-addpoint-append-and-shift/
     *         Append and shift
     * @sample highcharts/members/series-addpoint-x-and-y/
     *         Both X and Y values given
     * @sample highcharts/members/series-addpoint-pie/
     *         Append pie slice
     * @sample stock/members/series-addpoint/
     *         Append 100 points in Highstock
     * @sample stock/members/series-addpoint-shift/
     *         Append and shift in Highstock
     * @sample maps/members/series-addpoint/
     *         Add a point in Highmaps
     *
     * @function Highcharts.Series#addPoint
     *
     * @param {Highcharts.PointOptionsType} options
     *        The point options. If options is a single number, a point with
     *        that y value is appended to the series. If it is an array, it will
     *        be interpreted as x and y values respectively. If it is an
     *        object, advanced options as outlined under `series.data` are
     *        applied.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after the point is added. When adding
     *        more than one point, it is highly recommended that the redraw
     *        option be set to false, and instead {@link Chart#redraw} is
     *        explicitly called after the adding of points is finished.
     *        Otherwise, the chart will redraw after adding each point.
     *
     * @param {boolean} [shift=false]
     *        If true, a point is shifted off the start of the series as one is
     *        appended to the end.
     *
     * @param {boolean|Highcharts.AnimationOptionsObject} [animation]
     *        Whether to apply animation, and optionally animation
     *        configuration.
     *
     * @param {boolean} [withEvent=true]
     *        Used internally, whether to fire the series `addPoint` event.
     *
     * @return {void}
     *
     * @fires Highcharts.Series#event:addPoint
     */
    addPoint: function (options, redraw, shift, animation, withEvent) {
      var series = this,
        seriesOptions = series.options,
        data = series.data,
        chart = series.chart,
        xAxis = series.xAxis,
        names = xAxis && xAxis.hasNames && xAxis.names,
        dataOptions = seriesOptions.data,
        point,
        xData = series.xData,
        isInTheMiddle,
        i,
        x;
      // Optional redraw, defaults to true
      redraw = pick(redraw, true);
      // Get options and push the point to xData, yData and series.options. In
      // series.generatePoints the Point instance will be created on demand
      // and pushed to the series.data array.
      point = { series: series };
      series.pointClass.prototype.applyOptions.apply(point, [options]);
      x = point.x;
      // Get the insertion point
      i = xData.length;
      if (series.requireSorting && x < xData[i - 1]) {
        isInTheMiddle = true;
        while (i && xData[i - 1] > x) {
          i--;
        }
      }
      // Insert undefined item
      series.updateParallelArrays(point, "splice", i, 0, 0);
      // Update it
      series.updateParallelArrays(point, i);
      if (names && point.name) {
        names[x] = point.name;
      }
      dataOptions.splice(i, 0, options);
      if (isInTheMiddle) {
        series.data.splice(i, 0, null);
        series.processData();
      }
      // Generate points to be added to the legend (#1329)
      if (seriesOptions.legendType === "point") {
        series.generatePoints();
      }
      // Shift the first point off the parallel arrays
      if (shift) {
        if (data[0] && data[0].remove) {
          data[0].remove(false);
        } else {
          data.shift();
          series.updateParallelArrays(point, "shift");
          dataOptions.shift();
        }
      }
      // Fire event
      if (withEvent !== false) {
        fireEvent(series, "addPoint", { point: point });
      }
      // redraw
      series.isDirty = true;
      series.isDirtyData = true;
      if (redraw) {
        chart.redraw(animation); // Animation is set anyway on redraw, #5665
      }
    },
    /**
     * Remove a point from the series. Unlike the
     * {@link Highcharts.Point#remove} method, this can also be done on a point
     * that is not instanciated because it is outside the view or subject to
     * Highstock data grouping.
     *
     * @sample highcharts/members/series-removepoint/
     *         Remove cropped point
     *
     * @function Highcharts.Series#removePoint
     *
     * @param {number} i
     *        The index of the point in the {@link Highcharts.Series.data|data}
     *        array.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after the point is added. When
     *        removing more than one point, it is highly recommended that the
     *        `redraw` option be set to `false`, and instead {@link
     *        Highcharts.Chart#redraw} is explicitly called after the adding of
     *        points is finished.
     *
     * @param {boolean|Highcharts.AnimationOptionsObject} [animation]
     *        Whether and optionally how the series should be animated.
     *
     * @return {void}
     *
     * @fires Highcharts.Point#event:remove
     */
    removePoint: function (i, redraw, animation) {
      var series = this,
        data = series.data,
        point = data[i],
        points = series.points,
        chart = series.chart,
        remove = function () {
          if (points && points.length === data.length) {
            // #4935
            points.splice(i, 1);
          }
          data.splice(i, 1);
          series.options.data.splice(i, 1);
          series.updateParallelArrays(
            point || { series: series },
            "splice",
            i,
            1
          );
          if (point) {
            point.destroy();
          }
          // redraw
          series.isDirty = true;
          series.isDirtyData = true;
          if (redraw) {
            chart.redraw();
          }
        };
      setAnimation(animation, chart);
      redraw = pick(redraw, true);
      // Fire the event with a default handler of removing the point
      if (point) {
        point.firePointEvent("remove", null, remove);
      } else {
        remove();
      }
    },
    /**
     * Remove a series and optionally redraw the chart.
     *
     * @sample highcharts/members/series-remove/
     *         Remove first series from a button
     *
     * @function Highcharts.Series#remove
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart or wait for an explicit call to
     *        {@link Highcharts.Chart#redraw}.
     *
     * @param {boolean|Highcharts.AnimationOptionsObject} [animation]
     *        Whether to apply animation, and optionally animation
     *        configuration.
     *
     * @param {boolean} [withEvent=true]
     *        Used internally, whether to fire the series `remove` event.
     *
     * @return {void}
     *
     * @fires Highcharts.Series#event:remove
     */
    remove: function (redraw, animation, withEvent, keepEvents) {
      var series = this,
        chart = series.chart;
      /**
       * @private
       */
      function remove() {
        // Destroy elements
        series.destroy(keepEvents);
        series.remove = null; // Prevent from doing again (#9097)
        // Redraw
        chart.isDirtyLegend = chart.isDirtyBox = true;
        chart.linkSeries();
        if (pick(redraw, true)) {
          chart.redraw(animation);
        }
      }
      // Fire the event with a default handler of removing the point
      if (withEvent !== false) {
        fireEvent(series, "remove", null, remove);
      } else {
        remove();
      }
    },
    /**
     * Update the series with a new set of options. For a clean and precise
     * handling of new options, all methods and elements from the series are
     * removed, and it is initialized from scratch. Therefore, this method is
     * more performance expensive than some other utility methods like {@link
     * Series#setData} or {@link Series#setVisible}.
     *
     * Note that `Series.update` may mutate the passed `data` options.
     *
     * @sample highcharts/members/series-update/
     *         Updating series options
     * @sample maps/members/series-update/
     *         Update series options in Highmaps
     *
     * @function Highcharts.Series#update
     *
     * @param {Highcharts.SeriesOptionsType} options
     *        New options that will be merged with the series' existing options.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after the series is altered. If doing
     *        more operations on the chart, it is a good idea to set redraw to
     *        false and call {@link Chart#redraw} after.
     *
     * @return {void}
     *
     * @fires Highcharts.Series#event:update
     * @fires Highcharts.Series#event:afterUpdate
     */
    update: function (options, redraw) {
      options = H.cleanRecursively(options, this.userOptions);
      fireEvent(this, "update", { options: options });
      var series = this,
        chart = series.chart,
        // must use user options when changing type because series.options
        // is merged in with type specific plotOptions
        oldOptions = series.userOptions,
        seriesOptions,
        initialType = series.initialType || series.type,
        newType = options.type || oldOptions.type || chart.options.chart.type,
        keepPoints = !(
          // Indicators, histograms etc recalculate the data. It should be
          // possible to omit this.
          (
            this.hasDerivedData ||
            // Changes to data grouping requires new points in new groups
            options.dataGrouping ||
            // New type requires new point classes
            (newType && newType !== this.type) ||
            // New options affecting how the data points are built
            typeof options.pointStart !== "undefined" ||
            options.pointInterval ||
            options.pointIntervalUnit ||
            options.keys
          )
        ),
        initialSeriesProto = seriesTypes[initialType].prototype,
        n,
        groups = ["group", "markerGroup", "dataLabelsGroup", "transformGroup"],
        preserve = ["eventOptions", "navigatorSeries", "baseSeries"],
        // Animation must be enabled when calling update before the initial
        // animation has first run. This happens when calling update
        // directly after chart initialization, or when applying responsive
        // rules (#6912).
        animation = series.finishedAnimating && { animation: false },
        kinds = {};
      if (keepPoints) {
        preserve.push(
          "data",
          "isDirtyData",
          "points",
          "processedXData",
          "processedYData",
          "xIncrement",
          "_hasPointMarkers",
          "_hasPointLabels",
          // Map specific, consider moving it to series-specific preserve-
          // properties (#10617)
          "mapMap",
          "mapData",
          "minY",
          "maxY",
          "minX",
          "maxX"
        );
        if (options.visible !== false) {
          preserve.push("area", "graph");
        }
        series.parallelArrays.forEach(function (key) {
          preserve.push(key + "Data");
        });
        if (options.data) {
          // setData uses dataSorting options so we need to update them
          // earlier
          if (options.dataSorting) {
            extend(series.options.dataSorting, options.dataSorting);
          }
          this.setData(options.data, false);
        }
      }
      // Do the merge, with some forced options
      options = merge(
        oldOptions,
        animation,
        {
          // When oldOptions.index is null it should't be cleared.
          // Otherwise navigator series will have wrong indexes (#10193).
          index:
            typeof oldOptions.index === "undefined"
              ? series.index
              : oldOptions.index,
          pointStart: pick(
            // when updating from blank (#7933)
            oldOptions.pointStart,
            // when updating after addPoint
            series.xData[0]
          ),
        },
        !keepPoints && { data: series.options.data },
        options
      );
      // Merge does not merge arrays, but replaces them. Since points were
      // updated, `series.options.data` has correct merged options, use it:
      if (keepPoints && options.data) {
        options.data = series.options.data;
      }
      // Make sure preserved properties are not destroyed (#3094)
      preserve = groups.concat(preserve);
      preserve.forEach(function (prop) {
        preserve[prop] = series[prop];
        delete series[prop];
      });
      // Destroy the series and delete all properties. Reinsert all
      // methods and properties from the new type prototype (#2270,
      // #3719).
      series.remove(false, null, false, true);
      for (n in initialSeriesProto) {
        // eslint-disable-line guard-for-in
        series[n] = void 0;
      }
      if (seriesTypes[newType || initialType]) {
        extend(series, seriesTypes[newType || initialType].prototype);
      } else {
        H.error(17, true, chart, { missingModuleFor: newType || initialType });
      }
      // Re-register groups (#3094) and other preserved properties
      preserve.forEach(function (prop) {
        series[prop] = preserve[prop];
      });
      series.init(chart, options);
      // Remove particular elements of the points. Check `series.options`
      // because we need to consider the options being set on plotOptions as
      // well.
      if (keepPoints && this.points) {
        seriesOptions = series.options;
        // What kind of elements to destroy
        if (seriesOptions.visible === false) {
          kinds.graphic = 1;
          kinds.dataLabel = 1;
        } else if (!series._hasPointLabels) {
          var marker = seriesOptions.marker,
            dataLabels = seriesOptions.dataLabels;
          if (
            marker &&
            (marker.enabled === false || "symbol" in marker) // #10870
          ) {
            kinds.graphic = 1;
          }
          if (dataLabels && dataLabels.enabled === false) {
            kinds.dataLabel = 1;
          }
        }
        this.points.forEach(function (point) {
          if (point && point.series) {
            point.resolveColor();
            // Destroy elements in order to recreate based on updated
            // series options.
            if (Object.keys(kinds).length) {
              point.destroyElements(kinds);
            }
            if (seriesOptions.showInLegend === false && point.legendItem) {
              chart.legend.destroyItem(point);
            }
          }
        }, this);
      }
      // Update the Z index of groups (#3380, #7397)
      if (options.zIndex !== oldOptions.zIndex) {
        groups.forEach(function (groupName) {
          if (series[groupName]) {
            series[groupName].attr({
              zIndex: options.zIndex,
            });
          }
        });
      }
      series.initialType = initialType;
      chart.linkSeries(); // Links are lost in series.remove (#3028)
      fireEvent(this, "afterUpdate");
      if (pick(redraw, true)) {
        chart.redraw(keepPoints ? void 0 : false);
      }
    },
    /**
     * Used from within series.update
     *
     * @private
     * @function Highcharts.Series#setName
     *
     * @param {string} name
     *
     * @return {void}
     */
    setName: function (name) {
      this.name = this.options.name = this.userOptions.name = name;
      this.chart.isDirtyLegend = true;
    },
  }
);
// Extend the Axis.prototype for dynamic methods
extend(
  Axis.prototype,
  /** @lends Highcharts.Axis.prototype */ {
    /**
     * Update an axis object with a new set of options. The options are merged
     * with the existing options, so only new or altered options need to be
     * specified.
     *
     * @sample highcharts/members/axis-update/
     *         Axis update demo
     *
     * @function Highcharts.Axis#update
     *
     * @param {Highcharts.AxisOptions} options
     *        The new options that will be merged in with existing options on
     *        the axis.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after the axis is altered. If doing
     *        more operations on the chart, it is a good idea to set redraw to
     *        false and call {@link Chart#redraw} after.
     *
     * @return {void}
     */
    update: function (options, redraw) {
      var chart = this.chart,
        newEvents = (options && options.events) || {};
      options = merge(this.userOptions, options);
      // Color Axis is not an array,
      // This change is applied in the ColorAxis wrapper
      if (chart.options[this.coll].indexOf) {
        // Don't use this.options.index,
        // StockChart has Axes in navigator too
        chart.options[this.coll][
          chart.options[this.coll].indexOf(this.userOptions)
        ] = options;
      }
      // Remove old events, if no new exist (#8161)
      objectEach(chart.options[this.coll].events, function (fn, ev) {
        if (typeof newEvents[ev] === "undefined") {
          newEvents[ev] = void 0;
        }
      });
      this.destroy(true);
      this.init(chart, extend(options, { events: newEvents }));
      chart.isDirtyBox = true;
      if (pick(redraw, true)) {
        chart.redraw();
      }
    },
    /**
     * Remove the axis from the chart.
     *
     * @sample highcharts/members/chart-addaxis/
     *         Add and remove axes
     *
     * @function Highcharts.Axis#remove
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart following the remove.
     *
     * @return {void}
     */
    remove: function (redraw) {
      var chart = this.chart,
        key = this.coll, // xAxis or yAxis
        axisSeries = this.series,
        i = axisSeries.length;
      // Remove associated series (#2687)
      while (i--) {
        if (axisSeries[i]) {
          axisSeries[i].remove(false);
        }
      }
      // Remove the axis
      erase(chart.axes, this);
      erase(chart[key], this);
      if (isArray(chart.options[key])) {
        chart.options[key].splice(this.options.index, 1);
      } else {
        // color axis, #6488
        delete chart.options[key];
      }
      chart[key].forEach(function (axis, i) {
        // Re-index, #1706, #8075
        axis.options.index = axis.userOptions.index = i;
      });
      this.destroy();
      chart.isDirtyBox = true;
      if (pick(redraw, true)) {
        chart.redraw();
      }
    },
    /**
     * Update the axis title by options after render time.
     *
     * @sample highcharts/members/axis-settitle/
     *         Set a new Y axis title
     *
     * @function Highcharts.Axis#setTitle
     *
     * @param {Highcharts.AxisTitleOptions} titleOptions
     *        The additional title options.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after setting the title.
     *
     * @return {void}
     */
    setTitle: function (titleOptions, redraw) {
      this.update({ title: titleOptions }, redraw);
    },
    /**
     * Set new axis categories and optionally redraw.
     *
     * @sample highcharts/members/axis-setcategories/
     *         Set categories by click on a button
     *
     * @function Highcharts.Axis#setCategories
     *
     * @param {Array<string>} categories
     *        The new categories.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart.
     *
     * @return {void}
     */
    setCategories: function (categories, redraw) {
      this.update({ categories: categories }, redraw);
    },
  }
);
